{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Seria Lab: add a new ship (part 2)\n",
    "With the `seria` module being refactored. This _add a new ship_ chapter continues the old part and demonstrates how to add a new ship from a design file and add it to the player's profile with updated API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "import seria\n",
    "\n",
    "profile_path = 'profile.seria'\n",
    "profile = seria.load(profile_path)\n",
    "rook = seria.load('sample/Rook.seria')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# verify that the dump is identical to the original file\n",
    "seria.dump(profile, 'profile.seria')\n",
    "\n",
    "source = open('sample/profile.seria', encoding='cp1251')\n",
    "target = open('profile.seria', encoding='cp1251')\n",
    "\n",
    "print(source.read() == target.read())\n",
    "\n",
    "source.close()\n",
    "target.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get existing unique id values\n",
    "\n",
    "unique_ids = set()\n",
    "\n",
    "file = open(profile_path, 'r', encoding='cp1251')\n",
    "lines = file.readlines()\n",
    "for line in lines:\n",
    "    name, value = seria._match_attribute(line)\n",
    "    if name == 'm_id':\n",
    "        unique_ids.add(int(value))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "# make new id unique from existing ids\n",
    "next_id = max(unique_ids) + 1\n",
    "\n",
    "# obtain correct attribute values from profile\n",
    "next_creature_id = profile.get_attribute('nextCreatureId')\n",
    "\n",
    "p_fleet = profile.get_node_if(\n",
    "    lambda node: node.get_attribute('m_name') == 'MARK')\n",
    "\n",
    "alignment = p_fleet.get_attribute('m_alignment')\n",
    "escadra_id = p_fleet.get_attribute('m_id')\n",
    "\n",
    "p_ships = p_fleet.filter_nodes(lambda node: node.header == 'm_children=7')\n",
    "\n",
    "# make new escadra index for the new ship\n",
    "escadra_indexes = set()\n",
    "for ship in p_ships:\n",
    "    # Frame > Body > Creature\n",
    "    creature = ship.get_node_by_class('Frame').get_node(0).get_node(0)\n",
    "    escadra_indexes.add(int(creature.get_attribute('m_escadra_index')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Steps to configure a ship\n",
    "#### root _Node_ node\n",
    "1. Add a `header` of `m_children=7` to the beginning of the node.\n",
    "2. Make a new unique `m_id` for the root _Node_ node and update other nodes that reference the old value.\n",
    "3. Set `m_state` to `2`; this follows the values of other ships, which are `2`.\n",
    "4. Set `m_master_id` to be the `m_id` of the escadra which this ship belongs to.\n",
    "#### _Body_ nodes of root _Node_ node\n",
    "5. For all depth 2 _Body_ node of the root Node node. Make a new unique `m_id` for each _Body_ node. Update other nodes that reference the old value, this includes nodes in the same depth and nodes in a deeper depth (child nodes).\n",
    "6. Make a new unique `m_id` for the _Frame_ node and update other nodes that reference the old value.\n",
    "#### _Frame_ node\n",
    "7. For all depth 3 _Body_ node which is the child of Frame node. Make a new unique `m_id` for these nodes and update all other nodes that reference the old value.\n",
    "#### _Creature_ node\n",
    "8. For _Creature_ node, make a new unique `m_id` for it and update other nodes that reference the old value. _Creature_'s `m_owner_id` should reference its own `m_id`.\n",
    "9. set `creatureId` to the `nextCreatureId` obtained from the root node of the profile. Also update `nextCreatureId` because this one has been used by our new ship.\n",
    "10. set `m_escadra.id` to be the `m_id` of the escadra which this ship belongs to.\n",
    "11. set `m_alignment` to be `1` (friendly) or `-1` (enemy). This can be sampled from other ships in the same fleet.\n",
    "12. set `m_tarkhan` to be `DAUD`, or other NPCs that has joined your team.\n",
    "13. set `m_radiation_extra` to be `1` or can be sampled from other ships (but `1` works for me, so I just keep it).\n",
    "14. set `m_tele_crew_total` to be the same value as `m_tele_crew_capacity` obtained from the _Creature_ node. Same value means the crew is full.\n",
    "15. set `m_moral` to be `10`. You don't wanna ship to be added in a low moral, right?\n",
    "16. set `m_rad_seekness_timer` to be `5`, or you can sample and average it from other ships.\n",
    "17. set `shipLoadTime` to be `0`.\n",
    "18. set `wasAddedToPlayerEscadra` to be `true`.\n",
    "#### Bugs relate to misconfiguring attributes:\n",
    "If not set `m_rad_seekness_timer` and `m_radiation_extra`, in the game, when hovering the cursor on the ship select button and move away. The fuel will drop about 0.5% each time we repeat the action.  \n",
    "The order of attributes does matter. It is clear that if not set the attribute in the correct order. For example, set `m_tele_crew_total` after `m_tele_crew_capacity`, the `m_tele_crew_total` will be discarded by the game. This missing attribute can cause further bugs in the game.  \n",
    "The new `seria` module is designed to maintain the order of attributes and has provided APIs that allow you to insert attributes before and after another attribute. See the code sample below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "# update ship design with needed attributes obtained from profile\n",
    "rook.header = 'm_children=7'\n",
    "rook.put_attribute_after('m_state', '2', 'm_name')\n",
    "rook.put_attribute_after('m_master_id', escadra_id, 'm_state')\n",
    "\n",
    "# m_id for root Node node of the ship\n",
    "rook.update_attribute('m_id', str(next_id))\n",
    "\n",
    "# update all depth 2 nodes with new m_id\n",
    "for node in rook.get_nodes():\n",
    "    old_id = node.get_attribute('m_id')\n",
    "\n",
    "    next_id += 1\n",
    "    node.update_attribute('m_id', str(next_id))\n",
    "\n",
    "    # update all joint nodes with new m_id reference\n",
    "    for node in rook.get_nodes():\n",
    "        node.update_attribute_by_value(old_id, str(next_id))\n",
    "\n",
    "rook_frame = rook.get_node_by_class('Frame')\n",
    "next_id += 1\n",
    "rook_frame.update_attribute('m_id', str(next_id))\n",
    "\n",
    "# update all depth 3 nodes in frame node with new m_id\n",
    "for node in rook_frame.get_nodes():\n",
    "    old_id = node.get_attribute('m_id')\n",
    "\n",
    "    next_id += 1\n",
    "    node.update_attribute('m_id', str(next_id))\n",
    "\n",
    "    # update all joint nodes with new m_id reference\n",
    "    for node in rook.get_nodes():\n",
    "        node.update_attribute_by_value(old_id, str(next_id))\n",
    "\n",
    "rook_creature = rook_frame.get_node(0).get_node(0)\n",
    "next_id += 1\n",
    "old_creature_id = rook_creature.get_attribute('m_id')\n",
    "rook_creature.update_attribute('m_id', str(next_id))\n",
    "rook_creature.set_attribute('m_owner_id', str(next_id))\n",
    "rook.update_attribute_by_value(old_creature_id, str(next_id))\n",
    "\n",
    "rook_creature.set_attribute('creatureId', next_creature_id)\n",
    "profile.set_attribute('nextCreatureId', str(int(next_creature_id) + 1))\n",
    "\n",
    "rook_creature.put_attribute_after('m_escadra.id', escadra_id, 'm_health_lock')\n",
    "rook_creature.put_attribute_after('m_escadra_index', str(\n",
    "    max(escadra_indexes) + 1), 'm_escadra.id')\n",
    "rook_creature.put_attribute_after('m_alignment', alignment, 'm_playable')\n",
    "rook_creature.put_attribute_after('m_tarkhan', 'DAUD', 'm_alignment')\n",
    "rook_creature.put_attribute_after('m_radiation_extra', '1', 'm_tarkhan')\n",
    "\n",
    "crew_capacity = rook_creature.get_attribute('m_tele_crew_capacity')\n",
    "rook_creature.put_attribute_before(\n",
    "    'm_tele_crew_total', crew_capacity, 'm_tele_crew_capacity')\n",
    "rook_creature.put_attribute_before('m_moral', '10', 'creatureId')\n",
    "rook_creature.put_attribute_before('m_rad_seekness_timer', '5', 'shipLoadTime')\n",
    "rook_creature.put_attribute_before('shipLoadTime', '0', 'm_tele_fuel_on')\n",
    "\n",
    "rook_creature.set_attribute('wasAddedToPlayerEscadra', 'true')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "p_fleet.put_node_after(rook, p_ships[-1])\n",
    "\n",
    "seria.dump(profile, 'profile.seria')\n",
    "\n",
    "# seria.dump(rook, 'Rook.seria')\n",
    "\n",
    "# rook_sample = p_fleet.get_node_if(\n",
    "#     lambda node: node.get_attribute('m_name') == 'Rook')\n",
    "# seria.dump(rook_sample, 'Rook_sample.seria')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
